from otree.api import * author = "Nathaniel Archer Lawrence, LEMMA, Université Panthéon-Assas - Paris II" doc = """ Consumption simulation with interface based off of 'Shopping app (online grocery store)' from oTree demos, see: . """ def read_csv(): import csv f = open(__name__ + '/catalog.csv', encoding='utf-8-sig') rows = [row for row in csv.DictReader(f)] for row in rows: # all values in CSV are string unless you convert them row['unit_price'] = cu(row['unit_price']) # row['image_path'] = 'grocery/{}.png'.format(row['image_png']) return rows def read_csv_inflation(): import csv f = open(__name__ + '/animal_spirits_120.csv', encoding='utf-8-sig') rows = [row for row in csv.DictReader(f)] for row in rows: # all values in CSV are string unless you convert them row['rate'] = float(row['rate']) row['period'] = int(row['period']) return rows class C(BaseConstants): NAME_IN_URL = 'shop120' PLAYERS_PER_GROUP = None NUM_ROUNDS = 120 # consumption constants INITIAL_ENDOWMENT = 51568.99 INCOME = 3.99 INTEREST_RATE = 0.1 CONSUMPTION_RATE = 1 # amount of good consumed from stock balance each period MONETARY_POLICY = 0 # inflation parameters from csv INFLATION = read_csv_inflation() # period = ID in dict INFLATION_DICT = {row['period']: row for row in INFLATION} # list of products taken from csv file PRODUCTS = read_csv() # SKU = 'stock keeping unit' = product ID PRODUCTS_DICT = {row['sku']: row for row in PRODUCTS} class Subsession(BaseSubsession): def creating_session(self): if self.player.round_number > 1: print("PRODUCTS_DICT[Unit price] * 2: ", (C.PRODUCTS_DICT['1']['unit_price'] * 2)) else: print("PRODUCTS_DICT[Unit price]: ", (C.PRODUCTS_DICT['1']['unit_price'])) class Group(BaseGroup): pass class Player(BasePlayer): total_price = models.CurrencyField(initial=0) initial_savings = models.CurrencyField(initial=0) cashOnHand = models.CurrencyField(initial=0) finalSavings = models.CurrencyField(initial=0) finalStock = models.IntegerField(initial=0) interestEarned = models.CurrencyField(initial=0) decision = models.LongStringField(initial='') newPrice = models.CurrencyField(initial=C.PRODUCTS_DICT['1']['unit_price']) # for response time on each page responseTime = models.FloatField(initial=0) # for choice confidence questions current_choiceConfidence = models.StringField( choices = [['1','1 - Not confident at all'],['2','2'],['3','3'],['4','4'],['5','5 - Absolutely confident']], label='How confident are you with your decision?', widget=widgets.RadioSelect ) previous_choiceConfidence = models.StringField( choices = [['1','1 - Not confident at all'],['2','2'],['3','3'],['4','4'],['5','5 - Absolutely confident']], label='How confident are you with your decision in the previous period?', widget=widgets.RadioSelect ) # for perceived inflation measures (inflation estimation and price memory) inflationEstimation = models.FloatField( label='Over the course of the {} periods, by what percentage (%) would you estimate prices changed? (In other words, what do you estimate was the overall inflation rate?'.format(C.NUM_ROUNDS), min=-999999, max=999999 ) priceMemory_1 = models.CurrencyField( label='What price did you pay in Period 1?', min=0, max=100000 ) priceMemory_2 = models.CurrencyField( label='What price did you pay in Period {}?'.format(int(C.NUM_ROUNDS/3)), min=0, max=100000 ) priceMemory_3 = models.CurrencyField( label='What price did you pay in Period {}?'.format(int(C.NUM_ROUNDS*(2/3))), min=0, max=100000 ) priceMemory_4 = models.CurrencyField( label='What price did you pay in Period {}?'.format(C.NUM_ROUNDS), min=0, max=100000 ) class Item(ExtraModel): player = models.Link(Player) sku = models.StringField() name = models.StringField() quantity = models.IntegerField() unit_price = models.CurrencyField() newPrice = models.CurrencyField() # Calculate total price of item with quantity selected def total_price(item: Item): return item.quantity * item.unit_price # Convert information about items into a dictionary def to_dict(item: Item): return dict( sku=item.sku, name=item.name, quantity=item.quantity, total_price=total_price(item) ) def live_method(player: Player, data): import json # to convert dict to json print("NUM Rounds: ", C.NUM_ROUNDS) print("Round number: ", player.round_number) print("player new price csv: ", player.newPrice) if player.round_number == 1: player.newPrice = C.PRODUCTS_DICT['1']['unit_price'] * (1+C.INFLATION_DICT[player.round_number]['rate']) if 'sku' in data: sku = data['sku'] delta = data['delta'] product = C.PRODUCTS_DICT[sku] matches = Item.filter(player=player, sku=sku) if matches: [item] = matches item.quantity += delta if item.quantity <= 0: item.delete() else: if delta > 0: Item.create( player=player, quantity=delta, sku=sku, name=product['name'], unit_price=player.newPrice ) items = Item.filter(player=player) item_dicts = [to_dict(item) for item in items] print('item_dicts',item_dicts) player.total_price = sum([total_price(item) for item in items]) player.initial_savings = C.INITIAL_ENDOWMENT player.cashOnHand = player.initial_savings + C.INCOME player.finalSavings = player.cashOnHand - player.total_price player.finalStock = sum([item.quantity for item in items]) - C.CONSUMPTION_RATE player.interestEarned = 0 player.newPrice = player.newPrice else: player.newPrice = player.in_round(player.round_number - 1).newPrice * (1+C.INFLATION_DICT[player.round_number]['rate']) # allows subject to add and remove from shopping cart if 'sku' in data: sku = data['sku'] delta = data['delta'] product = C.PRODUCTS_DICT[sku] matches = Item.filter(player=player, sku=sku) if matches: [item] = matches item.quantity += delta if item.quantity <= 0: item.delete() else: if delta > 0: Item.create( player=player, quantity=delta, sku=sku, name=product['name'], unit_price=player.newPrice # update unit_price with newPrice from # newPrice=player.newPrice ) items = Item.filter(player=player) print('items: ', items) item_dicts = [to_dict(item) for item in items] print('item_dicts',item_dicts) player.total_price = sum([total_price(item) for item in items]) player.initial_savings = player.in_round(player.round_number - 1).finalSavings * (1 + (C.INTEREST_RATE + (C.MONETARY_POLICY * C.INFLATION_DICT[player.round_number - 1]['rate']))) # fetch previous period's final savings print("inflation rate: ", C.INFLATION_DICT[player.round_number]['rate']) print("monetary policy: ", 1 + (C.INTEREST_RATE + (C.MONETARY_POLICY * C.INFLATION_DICT[player.round_number]['rate']))) player.cashOnHand = player.initial_savings + C.INCOME player.finalSavings = player.cashOnHand - player.total_price player.finalStock = player.in_round(player.round_number - 1).finalStock + sum([item.quantity for item in items]) - C.CONSUMPTION_RATE # fetch previous period's final stock player.interestEarned = player.in_round(player.round_number - 1).finalSavings * (C.INTEREST_RATE) # allows subject to add and remove from shopping cart # if 'sku' in data: # sku = data['sku'] # delta = data['delta'] # product = C.PRODUCTS_DICT[sku] # matches = Item.filter(player=player, sku=sku) # if matches: # [item] = matches # item.quantity += delta # if item.quantity <= 0: # item.delete() # else: # if delta > 0: # Item.create( # player=player, # quantity=delta, # sku=sku, # name=product['name'], # unit_price=product['unit_price'], # # update unit_price with newPrice from # newPrice=unit_price*(1+C.INFLATION_HIGH) # ) # items = Item.filter(player=player) # item_dicts = [to_dict(item) for item in items] # print('item_dicts',item_dicts) # player.total_price = sum([total_price(item) for item in items]) # # itemJson = json.dumps(item_dicts) # # player.newPrice = itemJson['new price'][0] * (1 + C.INFLATION_HIGH) # # print('player.new Price',player.field_maybe_none('newPrice')) # print("Data: ", data) # # Calculate balances # # in first round, initial savings is just initial endowment, and final stock is just the quantity selected minus 1 # # if player.round_number == 1: # # player.initial_savings = C.INITIAL_ENDOWMENT # # player.cashOnHand = player.initial_savings + C.INCOME # # player.finalSavings = player.cashOnHand - player.total_price # # player.finalStock = sum([item.quantity for item in items]) - C.CONSUMPTION_RATE # # player.interestEarned = 0 # # player.newPrice = player.field_maybe_none('newPrice') # # in subsequent rounds, initial savings is (1 + interest rate)*(final savings from previous period) # else: # player.initial_savings = player.in_round(player.round_number - 1).finalSavings * (1 + C.INTEREST_RATE) # fetch previous period's final savings # player.cashOnHand = player.initial_savings + C.INCOME # player.finalSavings = player.cashOnHand - player.total_price # player.finalStock = player.in_round(player.round_number - 1).finalStock + sum([item.quantity for item in items]) - C.CONSUMPTION_RATE # fetch previous period's final stock # player.interestEarned = player.in_round(player.round_number - 1).finalSavings * (C.INTEREST_RATE) # player.newPrice = json.loads(player.in_round(player.round_number - 1).decision)['new price'][0] * (1 + C.INFLATION_HIGH) player.decision = json.dumps({"item":[item.sku for item in items],"quantity":[item.quantity for item in items],"price":[float(item.unit_price) for item in items]}) # print('Previous player decision: ', player.in_round(player.round_number - 1).decision) print('Current player decision: ', player.decision) print('Player new price: ', player.newPrice) print('Final: ', player.finalSavings) print('initial: ', player.initial_savings) return { player.id_in_group: dict( items=item_dicts, total_price=player.total_price, initial_savings=player.initial_savings, interestEarned=player.interestEarned, cashOnHand=player.cashOnHand, finalSavings=player.finalSavings, finalStock=player.finalStock, decision=player.decision, newPrice=player.newPrice ) } # PAGES class MyPage(Page): live_method = live_method form_model = 'player' form_fields = ['responseTime'] ### Choice confidence quesiton for period 1 should only ask about current decision class Results_1(Page): form_model = 'player' form_fields = ['current_choiceConfidence'] @staticmethod def vars_for_template(player: Player): return dict(items=Item.filter(player=player)) @staticmethod def is_displayed(player): return player.round_number == 1 ### Choice confidence quesiton after period 1 ask about both previous period's and current decision class Results_t(Page): form_model = 'player' form_fields = ['current_choiceConfidence','previous_choiceConfidence'] @staticmethod def vars_for_template(player: Player): return dict(items=Item.filter(player=player)) @staticmethod def is_displayed(player): return player.round_number > 1 class Inflation_estimate(Page): form_model = 'player' form_fields = ['inflationEstimation'] @staticmethod def is_displayed(player): return player.round_number == C.NUM_ROUNDS class Price_memory(Page): form_model = 'player' form_fields = ['priceMemory_1','priceMemory_10','priceMemory_20','priceMemory_30',] @staticmethod def is_displayed(player): return player.round_number == C.NUM_ROUNDS page_sequence = [MyPage, Results_1, Results_t, Inflation_estimate, Price_memory]